//!
/**
 **************************************************************************************************************************************************************
 * <RBCamera Implementation>
 **************************************************************************************************************************************************************
 * @file RBCamera.cpp
 **************************************************************************************************************************************************************/

/**************************************************************************************************************************************************************
 * Precompiled Header
 **************************************************************************************************************************************************************/
#include "RBMainPCH.h"

/**************************************************************************************************************************************************************
 * Includes
 **************************************************************************************************************************************************************/
#include "RBCamera.h"

#include "RBMath.h"
#include "RBStream.h"

/**************************************************************************************************************************************************************
 * Defines
 **************************************************************************************************************************************************************/

/**************************************************************************************************************************************************************
 * Classes implementation
 **************************************************************************************************************************************************************/
RB_IMPLEMENT_COMPONENT(RBCamera);


/**
 **************************************************************************************************************************************************************
 * Constructor
 **************************************************************************************************************************************************************/
RBCamera::RBCamera(RBEntityHandle entityHandle, RBTransformComponentHandle xForm)
: RBTSpatialComponent(entityHandle, xForm)
, m_bCustomProj(false)
{
}

/**
 **************************************************************************************************************************************************************
 * Destructor
 **************************************************************************************************************************************************************/
RBCamera::~RBCamera()
{
}

/**************************************************************************************************************************************************************
 * 
 **************************************************************************************************************************************************************/
void RBCamera::SetViewFrustum(float32 fFovY, float32 fAspectRatio, float32 fNear, float32 fFar, bool bOrtho)
{
    float32 top = bOrtho ? fFovY : RBMath::Tan<float32>(fFovY * 0.5f);
    float32 right = top * fAspectRatio;

    SetViewFrustum(-right, right, top, -top, fNear, fFar, bOrtho);

}

/**************************************************************************************************************************************************************
 * 
 **************************************************************************************************************************************************************/
void RBCamera::SetViewFrustum(float32 fLeft, float32 fRight, float32 fTop, float32 fBottom, float32 fNear, float32 fFar, bool bOrtho)
{

    m_fLeft     = fLeft;
    m_fRight    = fRight;
    m_fTop      = fTop;
    m_fBottom   = fBottom;
    m_fNear     = fNear;
    m_fFar      = fFar;
    m_bOrtho    = bOrtho;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBCamera::ChangeAspectRatio(float32 fAspectRatio)
{
    float32 fOldAspectRatio = (m_fRight - m_fLeft) / (m_fTop - m_fBottom);
    // We leave height with same values and change width

    m_fRight = m_fRight * fAspectRatio / fOldAspectRatio;
    m_fLeft = m_fLeft * fAspectRatio / fOldAspectRatio;

    SetViewFrustum(m_fLeft, m_fRight, m_fTop, m_fBottom, m_fNear, m_fFar, m_bOrtho);
}

/***********************************************************************************************************
 * Cloning
 ***********************************************************************************************************/
void RBCamera::copy_members(RBCamera* dest)
{
    dest->m_fLeft                   = m_fLeft;
    dest->m_fRight                  = m_fRight;
    dest->m_fTop                    = m_fTop;
    dest->m_fBottom                 = m_fBottom;
    dest->m_fNear                   = m_fNear;
    dest->m_fFar                    = m_fFar;
    dest->m_bOrtho                  = m_bOrtho;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBCamera::Serialize(RBStream& kStream)
{
    kStream & m_fLeft & m_fRight & m_fTop & m_fBottom & m_fNear & m_fFar & m_bOrtho;

    if (kStream.IsReading())
    {
        SetViewFrustum(m_fLeft, m_fRight, m_fTop, m_fBottom, m_fNear, m_fFar, m_bOrtho);
    }
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
float4x4 RBCamera::GetViewMatrix() const
{
    float4x4 mat;

    float3 kRight, kUp, kBack;
    WorldXForm().Rotate.GetCol(0, kRight);
    WorldXForm().Rotate.GetCol(1, kUp);
    WorldXForm().Rotate.GetCol(2, kBack);

    mat(0,0) = kRight.x;  mat(0,1) = kRight.y;  mat(0,2) = kRight.z;  mat(0,3) = -kRight.Dot(WorldXForm().Translate);
    mat(1,0) = kUp.x;     mat(1,1) = kUp.y;     mat(1,2) = kUp.z;     mat(1,3) = -kUp.Dot(WorldXForm().Translate);
    mat(2,0) = kBack.x;   mat(2,1) = kBack.y;   mat(2,2) = kBack.z;   mat(2,3) = -kBack.Dot(WorldXForm().Translate);
    mat(3,0) = 0;         mat(3,1) = 0;         mat(3,2) = 0;         mat(3,3) = 1;

    return mat;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
float4x4 RBCamera::BuildProjMatrix( const RBCamera& camera )
{
    float4x4 mat;

    float fRmL = camera.Right() - camera.Left();
    float fRpL = camera.Right() + camera.Left();
    float fTmB = camera.Top() - camera.Bottom();
    float fTpB = camera.Top() + camera.Bottom();
    float fFpN = camera.Far() + camera.Near();
    float fInvNmF = 1.0f / (camera.Near() - camera.Far());

    if (camera.Ortho())
    {
        mat(0,0) = 2.0f / fRmL;
        mat(0,1) = 0.0f;
        mat(0,2) = 0.0f; 
        mat(0,3) = -fRpL / fRmL; 
        mat(1,0) = 0.0f;
        mat(1,1) = 2.0f / fTmB;
        mat(1,2) = 0.0f; 
        mat(1,3) = -fTpB / fTmB;
        mat(2,0) = 0.0f;
        mat(2,1) = 0.0f;
        mat(2,2) = 2*fInvNmF;
        mat(2,3) = fFpN * fInvNmF; 
        mat(3,0) = 0.0f;
        mat(3,1) = 0.0f;
        mat(3,2) = 0.0f;
        mat(3,3) = 1.0f;               

    }
    else
    {
        mat(0,0) = 2.0f / fRmL;
        mat(0,1) = 0.0f;
        mat(0,2) = fRpL / fRmL;
        mat(0,3) = 0.0f;

        mat(1,0) = 0.0f;
        mat(1,1) = 2.0f / fTmB;
        mat(1,2) = fTpB / fTmB;
        mat(1,3) = 0.0f;

        mat(2,0) = 0.0f;
        mat(2,1) = 0.0f;
        mat(2,2) = (camera.Far() + camera.Near()) * fInvNmF;
        mat(2,3) = 2*camera.Near() * camera.Far() * fInvNmF;

        mat(3,0) =  0.0f;
        mat(3,1) =  0.0f;
        mat(3,2) = -1.0f;
        mat(3,3) =  0.0f;
    }

    return mat;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBCamera::LookAt( const float3& vDirection, const float3& vUp )
{
    float3 vRight = -vUp.Cross(vDirection).Normalize();
    float3 vNewUp = -vDirection.Cross(vRight);
    float* pCol;
    pCol = WorldXForm().Rotate.GetCol(CD_RIGHT);
    *pCol++ = vRight.x;
    *pCol++ = vRight.y;
    *pCol++ = vRight.z;
    pCol = WorldXForm().Rotate.GetCol(CD_UP);
    *pCol++ = vNewUp.x;
    *pCol++ = vNewUp.y;
    *pCol++ = vNewUp.z;
    pCol = WorldXForm().Rotate.GetCol(CD_BACK);
    *pCol++ = -vDirection.x;
    *pCol++ = -vDirection.y;
    *pCol++ = -vDirection.z;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
float4x4 RBCamera::LookAtDirection( const float3& vDirection, const float3& vUp )
{
    float4x4 mat;
    float3 vRight = -vUp.Cross(vDirection).Normalize();
    float3 vNewUp = -vDirection.Cross(vRight);

    mat(0,0) = vRight.x;      mat(0,1) = vRight.y;      mat(0,2) = vRight.z;      mat(0,3) = 0;
    mat(1,0) = vNewUp.x;      mat(1,1) = vNewUp.y;      mat(1,2) = vNewUp.z;      mat(1,3) = 0;
    mat(2,0) = -vDirection.x; mat(2,1) = -vDirection.y; mat(2,2) = -vDirection.z; mat(2,3) = 0;
    mat(3,0) = 0;             mat(3,1) = 0;             mat(3,2) = 0;             mat(3,3) = 1;

    return mat;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBCamera::EnableCustomProj( const float4x4& customProj )
{
    m_customProj = customProj;
    m_bCustomProj = true;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
float4x4 RBCamera::GetProjMatrix() const
{
    return m_bCustomProj ? m_customProj : BuildProjMatrix(*this);
}

/**************************************************************************************************************************************************************/
